package com.gigi.service.manual;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.Response;
import com.alibaba.cola.dto.SingleResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gigi.annotation.AuthCheck;
import com.gigi.common.Constant;
import com.gigi.common.PageRequest;
import com.gigi.entity.UserEntity;
import com.gigi.exception.BusinessException;
import com.gigi.model.dto.UserDTO;
import com.gigi.model.enums.ErrorCodeEnum;
import com.gigi.model.request.ListUserRequest;
import com.gigi.model.request.LoginRequest;
import com.gigi.model.request.RegisterRequest;
import com.gigi.model.request.SearchUsersRequest;
import com.gigi.service.generated.UserService;
import com.gigi.util.AlgorithmUtil;
import com.gigi.util.AuthorityUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 用户管理
 *
 * @author Gigi
 */
@Slf4j
@Service
public class UserManageService {

    @Autowired
    private UserService userService;

    @Autowired(required = false)
    private RedisTemplate redisTemplate;

    /**
     * 注册接口
     *
     * @param request
     * @return
     */
    public Response register(RegisterRequest request) {
        // 1、校验
        // 账号格式验证
        if (!(request.getUserAccount().length() >= 6 && request.getUserAccount().matches("[a-zA-Z0-9]+"))) {
            throw new BusinessException(ErrorCodeEnum.ACCOUNT_NOT_STRONG);
        }
        // 两次密码一致
        if (!request.getUserPassword().equals(request.getCheckPassword())) {
            throw new BusinessException(ErrorCodeEnum.PWD_NOT_SAME);
        }
        // 密码格式验证
        if (!request.getUserPassword().matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,10}$")) {
            throw new BusinessException(ErrorCodeEnum.PWD_NOT_STRONG);
        }
        // 账号唯一性验证
        LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>()
                .eq(UserEntity::getUserAccount, request.getUserAccount());
        if (userService.count(wrapper) > 0) {
            throw new BusinessException(ErrorCodeEnum.ALREADY_REGISTER);
        }
        // 2、加密
        String safetyPassword = DigestUtils.md5DigestAsHex((request.getUserPassword() + Constant.SALT).getBytes());
        // 3、入库
        UserEntity userEntity = new UserEntity();
        userEntity.setUserAccount(request.getUserAccount());
        userEntity.setUserPassword(safetyPassword);
        if (!userService.save(userEntity)) {
            throw new BusinessException(ErrorCodeEnum.SAVE_FAILED);
        }
        return Response.buildSuccess();
    }

    /**
     * 登录接口
     *
     * @param request
     * @param httpServletRequest
     * @return
     */
    public Response login(LoginRequest request, HttpServletRequest httpServletRequest) {
        // 1、校验
        // httpServletRequest不能为空
        if (Objects.isNull(httpServletRequest)) {
            throw new BusinessException(ErrorCodeEnum.PARAM_ERROR);
        }
        // 账号格式验证
        if (!(request.getUserAccount().length() >= 6 && request.getUserAccount().matches("[a-zA-Z0-9]+"))) {
            throw new BusinessException(ErrorCodeEnum.ACCOUNT_NOT_STRONG);
        }
        // 密码格式验证
        if (!request.getUserPassword().matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,10}$")) {
            throw new BusinessException(ErrorCodeEnum.PWD_NOT_STRONG);
        }
        // 2、加密
        String safetyPassword = DigestUtils.md5DigestAsHex((request.getUserPassword() + Constant.SALT).getBytes());
        // 3、查询
        LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>()
                .eq(UserEntity::getUserAccount, request.getUserAccount())
                .eq(UserEntity::getUserPassword, safetyPassword);
        UserEntity user = userService.getOne(wrapper, false);
        if (Objects.isNull(user)) {
            throw new BusinessException(ErrorCodeEnum.ACCOUNT_NOT_EXIST);
        }
        // 4、脱敏
        user.setUserPassword(null);
        // 5、记录登录态
        httpServletRequest.getSession().setAttribute(Constant.USER_LOGIN_SESSION_KEY, user);
        log.info("login: User {} logged in. Session ID: {}", user.getUserName(), httpServletRequest.getSession().getId());
        return Response.buildSuccess();
    }

    /**
     * 获取当前用户接口
     *
     * @param httpServletRequest
     * @return
     */
    public SingleResponse<UserEntity> currentUser(HttpServletRequest httpServletRequest) {
        return SingleResponse.of(AuthorityUtil.getLoginUser(httpServletRequest));
    }

    /**
     * 注销接口
     *
     * @param httpServletRequest
     * @return
     */
    public Response outLogin(HttpServletRequest httpServletRequest) {
        // 1、校验
        // httpServletRequest不能为空
        if (Objects.isNull(httpServletRequest)) {
            throw new BusinessException(ErrorCodeEnum.PARAM_ERROR);
        }
        // 2、清除登录态
        HttpSession session = httpServletRequest.getSession(false);
        if (Objects.nonNull(session)) {
            session.invalidate();
            log.info("outLogin: Session invalidated and removed from Redis.");
        } else {
            log.info("outLogin: No session found.");
        }
        return Response.buildSuccess();
    }

    /**
     * 列表接口
     *
     * @param request
     * @return
     */
    @AuthCheck(checkType = Constant.IS_ADMIN)
    public MultiResponse<UserEntity> list(ListUserRequest request, HttpServletRequest httpServletRequest) {
        // 1、查询
        LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>()
                .eq(StrUtil.isNotBlank(request.getUserAccount()), UserEntity::getUserAccount, request.getUserAccount())
                .like(StrUtil.isNotBlank(request.getUserName()), UserEntity::getUserName, request.getUserName())
                .orderByDesc(UserEntity::getId);
        Page<UserEntity> userEntityPage = userService.page(new Page<>(request.getPageNum(), request.getPageSize()), wrapper);
        if (Objects.isNull(userEntityPage) || CollUtil.isEmpty(userEntityPage.getRecords())) {
            return MultiResponse.ofWithoutTotal(Collections.emptyList());
        }
        // 2、脱敏
        userEntityPage.getRecords().forEach(userEntity -> userEntity.setUserPassword(null));
        return MultiResponse.of(userEntityPage.getRecords(), (int) userEntityPage.getTotal());
    }

    /**
     * 根据标签搜索用户接口（or）
     *
     * @param request
     * @return
     */
    @AuthCheck
    public MultiResponse<UserEntity> searchUserByTags(SearchUsersRequest request, HttpServletRequest httpServletRequest) {
        // 1、查库
        List<UserEntity> userEntities = searchBySQL(request);
        if (CollUtil.isEmpty(userEntities)) {
            return MultiResponse.ofWithoutTotal(Collections.emptyList());
        }
        // 2、脱敏
        userEntities.forEach(e -> e.setUserPassword(null));
        // 3、查总数
        LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<>();
        request.getTagList().forEach(e -> wrapper.or().like(UserEntity::getTags, StrUtil.format("\"{}\"", e)));
        // 4、返回
        return MultiResponse.of(userEntities, userService.count(wrapper));
    }

    /**
     * 方式一：SQL查询
     * 模糊查询指定标签用户
     * <p>
     * 数据量<10，list => 23ms【慢】
     * 数据量500w，pageSize=10 => and 1.42s、or 5.25s【慢】
     *
     * @param request
     * @return
     */
    private List<UserEntity> searchBySQL(SearchUsersRequest request) {
        LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(UserEntity::getId);
        request.getTagList().forEach(e -> wrapper.or().like(UserEntity::getTags, StrUtil.format("\"{}\"", e)));
        return userService.page(new Page<>(request.getPageNum(), request.getPageSize()), wrapper).getRecords();
    }

    /**
     * 方式二：内存查询
     * 先查所有用户，再过滤出含有指定标签的用户
     * <p>
     * 数据量<10，list => 7ms【快】
     * 数据量500w，pageSize=10 => 774.87ms【快】
     *
     * @param request
     * @return
     */
    private List<UserEntity> searchByMemory(SearchUsersRequest request) {
        LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>().orderByDesc(UserEntity::getId);
        List<UserEntity> userEntities = userService.page(new Page<>(request.getPageNum(), request.getPageSize()), wrapper).getRecords();
        if (CollUtil.isEmpty(userEntities)) {
            return Collections.emptyList();
        }
        Gson gson = new Gson();
        return userEntities.stream().filter(user -> {
            String tagsStr = user.getTags();
            Set<String> tempTagNameSet = gson.fromJson(tagsStr, new TypeToken<Set<String>>() {
            }.getType());
            tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());
            for (String tagName : request.getTagList()) {
                if (tempTagNameSet.contains(tagName)) {
                    return true;
                }
            }
            return false;
        }).collect(Collectors.toList());
    }

    /**
     * 编辑用户信息接口
     *
     * @param request
     * @param httpServletRequest
     * @return
     */
    public Response editInfo(UserDTO request, HttpServletRequest httpServletRequest) {
        // 1、管理员 or 自己可以修改
        AuthorityUtil.isAdminOrSelf(httpServletRequest, request.getId());
        // 2、用户是否存在
        UserEntity oldUser = userService.getById(request.getId());
        if (Objects.isNull(oldUser)) {
            throw new BusinessException(ErrorCodeEnum.NO_DATA);
        }
        // 3、如果更新标签，删推荐标签相似用户缓存
        if (Objects.nonNull(redisTemplate) && StrUtil.isNotBlank(request.getTags()) && !request.getTags().equals(oldUser.getTags())) {
            String pattern = "userHub:user:recommend:match:userId:" + request.getId() + ":pageNum:*:pageSize:*";
            Set<String> keys = redisTemplate.keys(pattern);
            if (keys != null && !keys.isEmpty()) {
                Long rows = redisTemplate.delete(keys);
                log.info("编辑用户信息，清除推荐标签相似用户缓存：{}，共{}条", pattern, rows);
            } else {
                log.info("编辑用户信息，未找到推荐标签相似用户缓存：{}", pattern);
            }
        }
        // 4、更新
        UserEntity userEntity = new UserEntity();
        BeanUtils.copyProperties(request, userEntity);
        if (!userService.updateById(userEntity)) {
            throw new BusinessException(ErrorCodeEnum.EDIT_FAILED);
        }
        // 5、查最新用户信息
        UserEntity newUser = userService.getById(request.getId());
        if (Objects.isNull(newUser)) {
            throw new BusinessException(ErrorCodeEnum.NO_DATA);
        }
        // 6、脱敏
        newUser.setUserPassword(null);
        // 7、更新登录态
        httpServletRequest.getSession(false).setAttribute(Constant.USER_LOGIN_SESSION_KEY, newUser);
        return Response.buildSuccess();
    }

    /**
     * 推荐接口
     * <p>
     * 使用 Redis 缓存首页高频访问的用户信息列表，将接口响应时长从 5s 缩短至 60ms ，
     * 且通过自定义 Redis 序列化器来解决数据乱码、空间浪费的问题。
     * <p>
     * 使用编辑距离算法随机匹配标签相似度高的用户。
     *
     * @param httpServletRequest
     * @return
     */
    public MultiResponse<UserEntity> recommend(PageRequest request, HttpServletRequest httpServletRequest) {
        // 1、参数校验
        if (Objects.isNull(httpServletRequest)) {
            throw new BusinessException(ErrorCodeEnum.PARAM_ERROR);
        }

        // 2、查缓存
        int flag = 0;
        String cacheKey = "";
        UserEntity loginUser = null;
        try {
            loginUser = AuthorityUtil.getLoginUser(httpServletRequest);
        } catch (BusinessException e) {
            // 未登录，推全量
            flag = 1;
            cacheKey = StrUtil.format(Constant.USER_RECOMMEND_FULL_CACHE_KEY, request.getPageNum(), request.getPageSize());
        }
        // 登录无标签，推全量
        if (Objects.nonNull(loginUser)) {
            if (StrUtil.isBlank(loginUser.getTags())) {
                flag = 1;
                cacheKey = StrUtil.format(Constant.USER_RECOMMEND_FULL_CACHE_KEY, request.getPageNum(), request.getPageSize());
            } else {
                // 登录有标签，推相似标签用户
                flag = 2;
                cacheKey = StrUtil.format(Constant.USER_RECOMMEND_MATCH_CACHE_KEY, loginUser.getId(), request.getPageNum(),
                        request.getPageSize());
            }
        }
        Page<UserEntity> userEntityPage = null;
        if (Objects.nonNull(redisTemplate)) {
            userEntityPage = (Page<UserEntity>) redisTemplate.opsForValue().get(cacheKey);
            if (Objects.nonNull(userEntityPage) && CollUtil.isNotEmpty(userEntityPage.getRecords())) {
                log.info("推荐用户：命中缓存，直接返回");
                return MultiResponse.of(userEntityPage.getRecords(), (int) userEntityPage.getTotal());
            }
        }

        // 3、没缓存查库
        // 推全量
        if (flag == 1) {
            LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>().orderByDesc(UserEntity::getId);
            userEntityPage = userService.page(new Page<>(request.getPageNum(), request.getPageSize()), wrapper);
        }
        // 推相似标签用户
        if (flag == 2) {
            userEntityPage = matchUsers(loginUser, request);
        }

        // 4、脱敏
        if (Objects.nonNull(userEntityPage) && CollUtil.isNotEmpty(userEntityPage.getRecords())) {
            userEntityPage.getRecords().forEach(e -> e.setUserPassword(null));
        }
        // 5、写缓存
        if (Objects.nonNull(redisTemplate)) {
            redisTemplate.opsForValue().set(cacheKey, userEntityPage, 24, TimeUnit.HOURS);
            log.info("推荐用户：查库并写缓存成功");
        } else {
            log.warn("推荐用户：未配置 Redis，不写缓存");
        }
        // 6、返回
        return MultiResponse.of(userEntityPage.getRecords(), (int) userEntityPage.getTotal());
    }

    /**
     * 匹配标签相似用户
     *
     * @param loginUser
     * @return
     */
    private Page<UserEntity> matchUsers(UserEntity loginUser, PageRequest request) {
        // 1、登录用户标签 json字符串 转 List
        Gson gson = new Gson();
        List<String> loginUserTags = gson.fromJson(loginUser.getTags(), new TypeToken<List<String>>() {
        }.getType());

        // 2、[分页]查出[除自己外][至少包含登录用户其中一个标签]的用户
        LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(UserEntity::getId);
        wrapper.ne(UserEntity::getId, loginUser.getId()).and(w -> {
            for (String tag : loginUserTags) {
                w.or().like(UserEntity::getTags, StrUtil.format("\"{}\"", tag));
            }
        });
        Page<UserEntity> page = userService.page(new Page<>(request.getPageNum(), request.getPageSize()), wrapper);
        if (Objects.isNull(page) || CollUtil.isEmpty(page.getRecords())) {
            return page;
        }

        // 3、依次计算标签相似度
        List<Pair<UserEntity, Long>> result = new ArrayList<>();
        for (UserEntity user : page.getRecords()) {
            List<String> userTags = gson.fromJson(user.getTags(), new TypeToken<List<String>>() {
            }.getType());
            long distance = AlgorithmUtil.minDistance(loginUserTags, userTags);
            result.add(new Pair<>(user, distance));
        }

        // 4、按编辑距离由小到大排序
        List<Pair<UserEntity, Long>> topUserPairList = result.stream()
                .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                .collect(Collectors.toList());

        // 5、返回
        Page<UserEntity> page1 = new Page<>(request.getPageNum(), request.getPageSize(), (int) page.getTotal());
        page1.setRecords(topUserPairList.stream().map(Pair::getKey).collect(Collectors.toList()));
        return page1;
    }

}
